package ide.ccf.ccproject;


import ide.ccf.ccproject.common.Utils;
import ide.ccf.ccproject.preferences.PreferenceConstants;
import ide.ccf.ccproject.qmake.IQMakeEnvironmentModifier;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.HashMap;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtension;
import org.eclipse.core.runtime.IExtensionPoint;
import org.eclipse.core.runtime.IExtensionRegistry;
import org.eclipse.core.runtime.ILog;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.IDocument;
import org.eclipse.ui.editors.text.TextEditor;
import org.eclipse.ui.part.FileEditorInput;
import org.eclipse.ui.plugin.AbstractUIPlugin;
import org.eclipse.ui.texteditor.IDocumentProvider;
import org.osgi.framework.BundleContext;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IPartService;
import org.eclipse.ui.IPartListener;
import org.eclipse.ui.IWindowListener;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IEditorReference;



/**
 * 用于桌面的主插件类
 *
 */
public class ProjectPlugin extends AbstractUIPlugin 
{
	private static ProjectPlugin plugin;
	private PorjectFileChangedListener proFileListener = new PorjectFileChangedListener();
	private Map<String, Boolean> filePathToQObjectMacro;
	private Map<String, String> filePathToProjectPath;
	private Map<String, Boolean> projectPathToRunQMake;
	private static List<IQMakeEnvironmentModifier> envModifiers = null;
	public static final String PLUGIN_ID = "ide.ccf.ccproject";

	/**
	 * 构造器
	 */
	public ProjectPlugin() 
	{
		super();
		
		plugin = this;
		filePathToQObjectMacro = new HashMap<String, Boolean>();
		filePathToProjectPath = new HashMap<String, String>();
		projectPathToRunQMake = new HashMap<String, Boolean>();
	}

	/**
	 * 判断是否需要执行qmake
	 * 如果项目中的任何一个源文件获得或失去Q_OBJECT宏，则返回true。对于每个已经打开的文件，我们会保存它是否包含Q_OBJECT的信息。
	 * 我们跟踪已经打开的编辑器的任何改变。一旦Q_OBJECT存在状态发生变化，我们将为它所属项目记录这条信息。
	 * 我们将在项目构建器(CCFMakefileGenerator)中检查这条信息并相应地运行qmake。而一旦运行qmake，也将清除该项目的这条信息。
	 * @param projectPath
	 * @return
	 */
	public boolean isRunningQMakeRequest(String projectPath) 
	{
		Boolean run = projectPathToRunQMake.get(projectPath);
		
		if (run != null && run.booleanValue() == true)
			return true;
		
		return false;
	}
	
	/**
	 * 清除qmake执行请求信息
	 * @param projectPath
	 */
	public void clearRunningQMakeRequest(String projectPath) 
	{
		projectPathToRunQMake.remove(projectPath);
	}
	
	/**
	 * 是否最终引用
	 * @param contents
	 * @param quoteIndex
	 * @return
	 */
	private boolean isEndingQuote(String contents, int quoteIndex)
	{
	    boolean endingQuote = true;
	    
	    if (quoteIndex > 0) 
	    {
	        int previous = 1;
	        while (contents.charAt(quoteIndex - previous) == '\\') 
	        {
	            previous++;
	            endingQuote = !endingQuote;
	        }
	    }
	    return endingQuote;
	}

	/**
	 * 内容是否包含Q_OBJECT宏
	 * @param contents
	 * @return
	 */
	private boolean hasQObjectMacro(String contents) 
	{
		int idx = 0;
		int count = contents.length();
		
		while (idx < count && idx >= 0) 
		{
			int macroIdx = contents.indexOf("Q_OBJECT", idx);
			
			if (macroIdx == -1) 
			{
				return false;
			} 
			else if (macroIdx == idx) 
			{
				return true;
			} 
			else if (contents.indexOf("//", idx) == idx) 
			{
				idx = contents.indexOf('\n', idx + 2) + 1;
			} 
			else if (contents.indexOf("/*", idx) == idx) 
			{
				idx = contents.indexOf("*/", idx + 2) + 2;
			} 
			else if (contents.indexOf("'\\\"'", idx) == idx) 
			{
				idx += 4;
			} 
			else if (contents.charAt(idx) == '\"') 
			{
	            do 
	            {
	                idx = contents.indexOf('\"', idx + 1);
	            } while (idx > 0 && !isEndingQuote(contents, idx));
	            
	            if (idx < 0)
	                return false;
	            
	            idx++;
			} 
			else 
			{
				idx++;
			}
		}
		return false;
	}

	/**
	 * 插件开始
	 */
	public void start(BundleContext context) throws Exception 
	{
		super.start(context);
		
		ResourcesPlugin.getWorkspace().addResourceChangeListener(proFileListener);
		ResourcesPlugin.getWorkspace().addResourceChangeListener(new IResourceChangeListener() 
		{
			private List<IFile> changedFiles = new ArrayList<IFile>();
			
			/**
			 * 资源变化事件处理
			 */
			public void resourceChanged(IResourceChangeEvent event) 
			{
				if (event.getType() != IResourceChangeEvent.POST_CHANGE)
					return;
				
				changedFiles.clear();
				visitChildren(event.getDelta());
				
				for (IFile changedFile : changedFiles) 
				{
					String changedFilePath = changedFile.getLocation().toOSString();
					//System.out.println("changedFilePath:"+changedFilePath);
					if (filePathToProjectPath.containsKey(changedFilePath)) 
					{
						boolean hadMacro = filePathToQObjectMacro.get(changedFilePath).booleanValue();
						boolean hasMacro = hasQObjectMacro(getContents(new FileEditorInput(changedFile)));
						
						// 这决定了是否执行qmake
						if (hadMacro != hasMacro) 
						{
							filePathToQObjectMacro.put(changedFilePath, hasMacro);
							projectPathToRunQMake.put(filePathToProjectPath.get(changedFilePath), true);
						}
					}
				}
				changedFiles.clear();
			}
			
			/**
			 * 获取编辑器内容
			 * @param input
			 * @return
			 */
			private String getContents(IFileEditorInput input) 
			{
				IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
				IEditorPart part = page.findEditor(input);
				
	            if (part instanceof TextEditor) 
	            {
	               	TextEditor te = (TextEditor)part;
	               	IDocumentProvider provider = te.getDocumentProvider();
	               	IDocument document = provider.getDocument(te.getEditorInput());
	               	String contents = document.get();
	               	
	              	return contents;
	            }		
				return null;
			}
			
			/**
			 * 访问子节点
			 * @param delta
			 */
			private void visitChildren(IResourceDelta delta) 
			{
				IResourceDelta[] changedChildren = delta.getAffectedChildren(IResourceDelta.CHANGED);
				
				if (changedChildren.length > 0) 
				{
					for (IResourceDelta child : changedChildren) 
					{
						if ((child.getFlags() & IResourceDelta.MARKERS) != 0) 
						{
							continue;
						}

						IResource resource = child.getResource();
						
						if (resource != null) 
						{
							if (resource instanceof IFile) 
							{
								changedFiles.add((IFile)resource);
							} 
							else if (resource instanceof IFolder) 
							{
								visitChildren(child);
							} 
							else if (resource instanceof IProject) 
							{
								visitChildren(child);
							}
						}
					}
				}
			}
		});
		
		IWorkbench workbench = PlatformUI.getWorkbench();

		workbench.addWindowListener(new IWindowListener() 
		{
			public void windowActivated(IWorkbenchWindow window) 
			{
				
			}
			
			public void windowClosed(IWorkbenchWindow window) 
			{

			}

			public void windowDeactivated(IWorkbenchWindow window) 
			{
				
			}

			public void windowOpened(IWorkbenchWindow window) 
			{
				addPartListener(window);
		    } 
		});
		
		IWorkbenchWindow[] windows = workbench.getWorkbenchWindows();
		
		for (IWorkbenchWindow window : windows)
			addPartListener(window);
	}
	
	/**
	 * 添加IWorkbenchPart侦听器
	 * @param window
	 */
	public void addPartListener(IWorkbenchWindow window) 
	{
        IPartService service = window.getPartService();
        
        service.addPartListener(new IPartListener() 
        {
        	/**
        	 * 构造器
        	 */
			public void partActivated(IWorkbenchPart part) 
			{

			}
			
			/**
			 * 获取内容
			 * @param part
			 * @return
			 */
			private String getContents(IWorkbenchPart part) 
			{
                if (part instanceof TextEditor) 
                {
                	TextEditor te = (TextEditor)part;
                	IDocumentProvider provider = te.getDocumentProvider();
                	IDocument document = provider.getDocument(te.getEditorInput());
                	String contents = document.get();
                	
                	return contents;
                }		
				return null;
			}
			
			/**
			 * 获取面向文件的编辑器输入
			 * @param part
			 * @return
			 */
			private IFileEditorInput getEditorInput(IWorkbenchPart part) 
			{
				if (part instanceof TextEditor) 
				{
					TextEditor te = (TextEditor)part;
					IEditorInput ei = te.getEditorInput();
					
					// 检查文件是否属于符合CCF项目
					if (Utils.isCCFProject(Utils.projectOfEditorInput(ei))) 
					{
						if (ei instanceof IFileEditorInput) 
						{
							IFileEditorInput fei = (IFileEditorInput)ei;
							return fei;
						}
					}
				}
				return null;
			}
			
			/**
			 * 获取文件路径
			 * @param fei
			 * @return
			 */
			private String getFilePath(IFileEditorInput fei) 
			{
				if (fei != null)
				{
					if(fei.getFile().getLocation() !=  null)
					{
						return fei.getFile().getLocation().toOSString();
					}
					else
						return null;
				}
					
				
				return null;
			}
			
			/**
			 * 获取文件路径
			 * @param part
			 * @return
			 */
			@SuppressWarnings("unused")
			private String getFilePath(IWorkbenchPart part) 
			{
				IFileEditorInput fei = getEditorInput(part);
				
				if (fei != null)
					return getFilePath(fei);
				
				return null;
			}
				
			/**
			 * 保存Q_OBJECT宏数据
			 * @param part
			 */
			private void storeQObjectMacroData(IWorkbenchPart part) 
			{
				IFileEditorInput fei = getEditorInput(part);
				String filePath = getFilePath(fei);

				if (filePath != null) 
				{
					IPath path = Utils.findProFile(fei.getFile().getProject());
					if(path == null)
						return;
					String proFilePath = path.toOSString();
					if (filePath.equals(proFilePath)) 
					{
						return;
					}

					if (!filePathToQObjectMacro.containsKey(filePath)) 
					{
						IProject project = Utils.projectOfEditorInput(fei);
						IPath proPath = Utils.findProFile(project);
						
						if (proPath != null) 
						{
						    filePathToProjectPath.put(filePath, proPath.toOSString());
							filePathToQObjectMacro.put(filePath, hasQObjectMacro(getContents(part)));
						}
					}
				}
			}

			/**
			 * IWorkbenchPart显示到顶部
			 */
			public void partBroughtToTop(IWorkbenchPart part) 
			{
				storeQObjectMacroData(part);
			}

			/**
			 * IWorkbenchPart关闭
			 */
			public void partClosed(IWorkbenchPart part) 
			{
				IFileEditorInput fei = getEditorInput(part);
				if (fei != null) 
				{
					IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
					
					if (window == null)
						return;
					
					IWorkbenchPage page = window.getActivePage();
					
					if (page == null)
						return;
					
					IEditorReference refs[] = page.findEditors(fei, null, org.eclipse.ui.IWorkbenchPage.MATCH_INPUT);

					if (refs.length == 0) 
					{
						String filePath = getFilePath(fei);

						filePathToQObjectMacro.remove(filePath);
						filePathToProjectPath.remove(filePath);
					}
				}
			}

			/**
			 * IWorkbenchPart取消激活
			 */
			public void partDeactivated(IWorkbenchPart part) 
			{
				
			}

			/**
			 * IWorkbenchPart打开
			 */
			public void partOpened(IWorkbenchPart part) 
			{
				storeQObjectMacroData(part);
	  		}
        });
	}

	/**
	 * 插件停止
	 */
	public void stop(BundleContext context) throws Exception 
	{
		super.stop(context);
		
		ResourcesPlugin.getWorkspace().removeResourceChangeListener(proFileListener);
		plugin = null;
	}

	/**
	 * 返回插件共享实例
	 */
	public static ProjectPlugin getDefault() 
	{
		return plugin;
	}

	/**
	 * 获取默认CCF版本
	 * @return 默认CCF版本或空字符串
	 */
	public String getDefaultCCFVersion() 
	{
		return ProjectPlugin.getDefault().getPreferenceStore().getString(PreferenceConstants.CCFVERSION_DEFAULT);
	}
	
	/**
	 * 添加一个新的CCF版本并且设为默认
	 * @param versionName
	 * @param binDir
	 * @param incDir
	 */
	public void addDefaultCCFVersion(String versionName, IPath binDir, IPath incDir) 
	{
		IPreferenceStore store = getPreferenceStore();

		int count = store.getInt(PreferenceConstants.CCFVERSION_COUNT);
		
		store.setValue(PreferenceConstants.CCFVERSION_COUNT, count + 1);
		store.setValue(PreferenceConstants.CCFVERSION_NAME + "." + Integer.toString(count), versionName);
		store.setValue(PreferenceConstants.CCFVERSION_BINPATH + "." + Integer.toString(count), binDir.toOSString());
		store.setValue(PreferenceConstants.CCFVERSION_INCLUDEPATH+ "." + Integer.toString(count), incDir.toOSString());
		store.setValue(PreferenceConstants.CCFVERSION_DEFAULT, versionName);
	}

	/**
	 * 返回注册的qmake环境变量修改列表
	 * @return
	 */
	public List<IQMakeEnvironmentModifier> getEnvironmentModifierExtensions() 
	{
		if (envModifiers == null) 
		{
			envModifiers = new ArrayList<IQMakeEnvironmentModifier>();

			IExtensionRegistry extensionRegistry = Platform.getExtensionRegistry();
			IExtensionPoint extensionPoint = extensionRegistry.getExtensionPoint(PLUGIN_ID + ".qmakeEnvironmentModifier");
			IExtension[] extensions = extensionPoint.getExtensions();
			
			for (int i = 0; i < extensions.length; i++) 
			{
				IExtension extension = extensions[i];
				IConfigurationElement[] elements = extension.getConfigurationElements();
				IConfigurationElement element = elements[0];
				
				boolean failed = false;
				
				try 
				{
					Object extObject = element.createExecutableExtension("class");
					
					if (extObject instanceof IQMakeEnvironmentModifier) 
					{
						envModifiers.add((IQMakeEnvironmentModifier)extObject);
					} 
					else 
					{
						failed = true;
					}
				} 
				catch (CoreException e) 
				{
					failed = true;
				}
				
				if (failed) 
				{
					logErrorMessage("无法装载qmakeEnvironmentModifier扩展：" + extension.getContributor().getName()); //$NON-NLS-1$
				}
			}
		}
		return envModifiers;
	}
	
	/**
	 * 记录给定错误字符串到错误日志中
	 * @param errMsg
	 */
	public void logErrorMessage(String errMsg) 
	{
		ILog log = plugin.getLog();
		
		if (log != null) 
		{
			log.log(new Status(IStatus.ERROR, PLUGIN_ID, errMsg));
		}
	}
}